﻿/*              Copyright(c) 2008 Dave Sexton              *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using DaveSexton.DocToMaml.Maml;

namespace DaveSexton.DocToMaml
{
	internal sealed class FileInput : InputBase
	{
		#region Public Properties
		public string Contents
		{
			get
			{
				if (contents == null)
					Load();

				return contents;
			}
			set
			{
				if (string.IsNullOrEmpty(value))
					throw new ArgumentException(Resources.Errors.NullOrEmpty, "value");

				if (HasResult)
				{
					if (resultModified)
						throw new InvalidOperationException(Resources.Errors.CannotSetContentsWithModifiedResult);

					resultDirty = true;
				}

				contents = value;
			}
		}

		public string Result
		{
			get
			{
				if (!HasResult || (resultDirty && !resultModified))
					Convert();

				return result;
			}
			set
			{
				if (!string.Equals(result, value, StringComparison.Ordinal))
				{
					Guid guid = VerifyXmlDocument(value);

					if (guid == Guid.Empty)
					{
						Guids.RemoveGuid(FullPath);

						result = null;
						resultDirty = false;
						resultModified = false;
						conceptualArtFileXml = null;
						conceptualArtFileXmlDirty = false;
					}
					else
					{
						result = value;

						Guids.SetGuid(FullPath, guid);

						resultDirty = false;
						resultModified = true;

						// TODO: Parse the result to get the updated images and links.
						// This flag is being set here for future use.  It will have no effect now since the result will not be parsed.
						conceptualArtFileXmlDirty = true;
					}
				}
			}
		}

		public bool HasResult
		{
			get
			{
				return result != null;
			}
		}

		public bool ResultModified
		{
			get
			{
				return resultModified;
			}
			set
			{
				resultModified = value;
			}
		}

		public string ResultFile { get; private set; }

		public override ICollection<Exception> ConversionErrors
		{
			get
			{
				if (conversionError == null)
					return new List<Exception>(0).AsReadOnly();
				else
					return new List<Exception>(new[] { conversionError }).AsReadOnly();
			}
		}

		public int UniqueImageCount
		{
			get
			{
				return Images.GetImageCountForInput(this);
			}
		}

		public int ProcessedImageCount { get; private set; }

		public int ProcessedLinkCount { get; private set; }

		public override string ConceptualArtFileXml
		{
			get
			{
				if (HasResult && (conceptualArtFileXml == null || conceptualArtFileXmlDirty))
					CreateConceptualArtFileXml();

				return conceptualArtFileXml;
			}
		}

		public override IEnumerable<string> Files
		{
			get
			{
				yield return FullPath;
			}
		}

		public override ICollection<IInput> Subinputs
		{
			get
			{
				return null;
			}
		}
		#endregion

		#region Private / Protected
		private string contents, result, conceptualArtFileXml;
		private bool resultDirty, conceptualArtFileXmlDirty, resultModified;
		private Exception conversionError;
		private TimeSpan conversionTime;
		private DateTime conversionStart;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="FileInput" /> class.
		/// </summary>
		public FileInput(string path, ConceptualImageManager imageManager, FileGuidManager guidManager)
			: base(path, imageManager, guidManager)
		{
			Rules.CollectionChange += (o, e) => resultDirty = true;
		}
		#endregion

		#region Methods
		private Guid VerifyXmlDocument(string xml)
		{
			if (xml == null || (xml = xml.Trim()).Length == 0)
				return Guid.Empty;

			Guid id = Guid.Empty;

			int indexOfXmlDeclarationEnd = xml.IndexOf("?>", StringComparison.Ordinal);

			if (indexOfXmlDeclarationEnd > -1)
				// The XML declaration probably specifies UTF-8 even though the document is UTF-16 since all .NET strings are Unicode.
				// If so, an exception is thrown when reader.MoveToContent is called (something like, "The data at position 1 is invalid").
				// Therefore, the declaration must be removed.
				xml = xml.Substring(indexOfXmlDeclarationEnd + 2);

			using (System.IO.StringReader stringReader = new System.IO.StringReader(xml))
			{
				XmlReaderSettings settings = new XmlReaderSettings();
				settings.IgnoreComments = true;
				settings.IgnoreProcessingInstructions = true;
				settings.IgnoreWhitespace = true;

				using (XmlReader reader = XmlReader.Create(stringReader, settings))
				{
					reader.MoveToContent();

					try
					{
						id = new Guid(reader.GetAttribute("id"));

						if (id == Guid.Empty)
							throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
								Resources.Errors.ResultInvalidGuid, Name));
					}
					catch (FormatException ex)
					{
						throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.CurrentCulture,
							Resources.Errors.ResultInvalidGuid, Name), ex);
					}

					while (reader.Read())
					{
						// do nothing except parse the XML
					}
				}
			}

			return id;
		}

		public void Load()
		{
			if (HasResult)
				resultDirty = true;

			contents = System.IO.File.ReadAllText(FullPath);
		}

		public override int Convert(string targetFolder)
		{
			try
			{
				string xml = Result;  // read Result property, not its backing field

				string target = System.IO.Path.Combine(targetFolder, Guids.GetOrCreateGuid(FullPath) + ".xml");

				ResultFile = target;

				System.IO.File.WriteAllText(target, xml, Encoding.UTF8);

				return 1;
			}
			catch (Exception ex)
			{
				conversionError = ex;
				throw;
			}
		}

		private void Convert()
		{
			Images.BeginConversionContext(this);

			try
			{
				conversionStart = DateTime.Now;

				HtmlToMamlConverter converter = new HtmlToMamlConverter(Rules, Images, Guids);

				Guid guid = Guids.GetOrCreateGuid(FullPath);

				if (contents == null)
					result = converter.ConvertFileToMaml(FullPath, guid);
				else
					result = converter.ConvertToMaml(contents, guid);

				resultDirty = false;

				// the cached conceptual art file needs to be recreated in case the images have changed
				conceptualArtFileXmlDirty = true;

				conversionTime = DateTime.Now - conversionStart;
				ProcessedImageCount = converter.ProcessedImageCount;
				ProcessedLinkCount = converter.ProcessedLinkCount;

				conversionError = null;
			}
			finally
			{
				Images.EndConversionContext();
			}
		}

		public void SetError(Exception ex)
		{
			conversionError = ex;
		}

		private void CreateConceptualArtFileXml()
		{
			conceptualArtFileXml = Images.CreateConceptualArtFileXml(this);
			conceptualArtFileXmlDirty = false;
		}

		protected override void WriteLogStart(System.IO.TextWriter writer)
		{
			writer.WriteLine("File: {0}", FullPath);

			if (HasResult)
			{
				writer.WriteLine("Output: {0}", ResultFile ?? Resources.Text.InMemoryConversion);
				writer.WriteLine("Guid: {0}", Guids.GetOrCreateGuid(FullPath));
			}
			else
			{
				writer.WriteLine();
				writer.WriteLine(Resources.Text.NotConverted);
			}
		}

		protected override void WriteLogContents(System.IO.TextWriter writer)
		{
			base.WriteLogContents(writer);

			if (HasResult)
			{
				writer.WriteLine("Conversion start time: {0:T}", conversionStart);
				writer.WriteLine("Conversion total time: {0}", conversionTime);
				writer.WriteLine();
				writer.WriteLine("# images: {0}", ProcessedImageCount);
				writer.WriteLine("# unique images: {0}", UniqueImageCount);
				writer.WriteLine("# links: {0}", ProcessedLinkCount);

				if (conversionError != null)
				{
					writer.WriteLine();
					writer.WriteLine(conversionError.ToString());
				}
			}
			else
			{
				if (conversionError != null)
					writer.WriteLine(conversionError.ToString());
			}
		}
		#endregion
	}
}
